﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace DDOLayoutEditor
{
    // See http://denisvuyka.wordpress.com/2007/10/13/the-wpf-resizing-adorner-for-canvas/
    // Modified for my use

    public enum ResizeTypes
    {
        Unknown,
        TopLeft,
        TopRight,
        BottomLeft,
        BottomRight
    }

    public class ResizeArgs
    {
        public bool Cancel { get; set; }
        public ResizeTypes ResizeType { get; set; }

        public FrameworkElement Element { get; set; }
        public Point Position { get; set; }
        public Size Size { get; set; }
    }

    public delegate void ResizeEventHandler(ResizeArgs e);
    public delegate void MouseEventHandler(MouseButtonEventArgs e);

    public class ResizingAdorner : Adorner
    {
        public static event ResizeEventHandler ResizeEvent;
        public static event MouseEventHandler MouseEvent;

        // Resizing adorner uses Thumbs for visual elements.  
        // The Thumbs have built-in mouse input handling.
        Thumb topLeft, topRight, bottomLeft, bottomRight;

        // To store and manage the adorner's visual children.
        VisualCollection visualChildren;

        bool allowResize;

        // Initialize the ResizingAdorner.
        public ResizingAdorner(UIElement adornedElement, bool allowResize) : base(adornedElement)
        {
            this.allowResize = allowResize;
            visualChildren = new VisualCollection(this);

            // Call a helper method to initialize the Thumbs
            // with a customized cursors.
            BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
            BuildAdornerCorner(ref topRight, Cursors.SizeNESW);
            BuildAdornerCorner(ref bottomLeft, Cursors.SizeNESW);
            BuildAdornerCorner(ref bottomRight, Cursors.SizeNWSE);
                        
            if (allowResize)
            {
                // Add handlers for resizing.
                bottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
                bottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);

                topLeft.DragDelta += new DragDeltaEventHandler(HandleTopLeft);
                topRight.DragDelta += new DragDeltaEventHandler(HandleTopRight);
            }
        }

        // Handler for resizing from the bottom-right.
        void HandleBottomRight(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null)
                return;

            FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            var resizeArgs = new ResizeArgs
            {
                ResizeType = ResizeTypes.BottomRight,
                Element = adornedElement,
                Position = new Point(
                        Canvas.GetLeft(adornedElement), 
                        Canvas.GetTop(adornedElement)),
                Size = new Size(
                        (double)Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width),
                        (double)Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height))
            };

            // Cheap static hook into resize
            OnCalculateSize(resizeArgs);

            if (resizeArgs.Cancel)
                return;

            // Change the size by the amount the user drags the mouse, as long as it's larger 
            // than the width or height of an adorner, respectively.
            adornedElement.Width = resizeArgs.Size.Width;
            adornedElement.Height = resizeArgs.Size.Height;
        }

        // Handler for resizing from the top-right.
        void HandleTopRight(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = this.AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;
            FrameworkElement parentElement = adornedElement.Parent as FrameworkElement;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            double height_old = adornedElement.Height;
            double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
            double top_old = Canvas.GetTop(adornedElement);

            var resizeArgs = new ResizeArgs
            {
                ResizeType = ResizeTypes.TopRight,
                Element = adornedElement,
                Position = new Point(
                        Canvas.GetLeft(adornedElement),
                        (double)top_old - (height_new - height_old)
                ),
                Size = new Size(
                        (double)Math.Max(adornedElement.Width + args.HorizontalChange, hitThumb.DesiredSize.Width),
                        (double)height_new
                )
            };

            // Cheap static hook into resize
            OnCalculateSize(resizeArgs);

            if (resizeArgs.Cancel)
                return;

            adornedElement.Width = resizeArgs.Size.Width;
            adornedElement.Height = resizeArgs.Size.Height;

            Canvas.SetTop(adornedElement, resizeArgs.Position.Y);
        }

        // Handler for resizing from the top-left.
        void HandleTopLeft(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            double width_old = adornedElement.Width;
            double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            double left_old = Canvas.GetLeft(adornedElement);

            double height_old = adornedElement.Height;
            double height_new = Math.Max(adornedElement.Height - args.VerticalChange, hitThumb.DesiredSize.Height);
            double top_old = Canvas.GetTop(adornedElement);

            var resizeArgs = new ResizeArgs
            {
                ResizeType = ResizeTypes.TopLeft,
                Element = adornedElement,
                Position = new Point(
                        (double)left_old - (width_new - width_old),
                        (double)top_old - (height_new - height_old)
                ),
                Size = new Size(
                        (double)width_new,
                        (double)height_new
                )
            };

            // Cheap static hook into resize
            OnCalculateSize(resizeArgs);

            if (resizeArgs.Cancel)
                return;
                        
            adornedElement.Width = resizeArgs.Size.Width;
            Canvas.SetLeft(adornedElement, resizeArgs.Position.X);
                        
            adornedElement.Height = resizeArgs.Size.Height;
            Canvas.SetTop(adornedElement, resizeArgs.Position.Y);
        }

        // Handler for resizing from the bottom-left.
        void HandleBottomLeft(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement adornedElement = AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (adornedElement == null || hitThumb == null) return;

            // Ensure that the Width and Height are properly initialized after the resize.
            EnforceSize(adornedElement);

            double width_old = adornedElement.Width;
            double width_new = Math.Max(adornedElement.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            double left_old = Canvas.GetLeft(adornedElement);

            var resizeArgs = new ResizeArgs
            {
                ResizeType = ResizeTypes.BottomLeft,
                Element = adornedElement,
                Position = new Point(
                        (double)left_old - (width_new - width_old),
                        (double)Canvas.GetTop(adornedElement)
                ),
                Size = new Size(
                        (double)width_new,
                        (double)Math.Max(args.VerticalChange + adornedElement.Height, hitThumb.DesiredSize.Height)
                )
            };

            // Cheap static hook into resize
            OnCalculateSize(resizeArgs);

            if (resizeArgs.Cancel)
                return;
            
            adornedElement.Height = resizeArgs.Size.Height;
            adornedElement.Width = resizeArgs.Size.Width;

            Canvas.SetLeft(adornedElement, resizeArgs.Position.X);
        }

        protected void OnCalculateSize(ResizeArgs args)
        {
            var evnt = ResizeEvent;

            if (evnt != null)
                evnt(args);
        }

        // Arrange the Adorners.
        protected override Size ArrangeOverride(Size finalSize)
        {
            // desiredWidth and desiredHeight are the width and height of the element that's being adorned.  
            // These will be used to place the ResizingAdorner at the corners of the adorned element.  
            double desiredWidth = AdornedElement.DesiredSize.Width;
            double desiredHeight = AdornedElement.DesiredSize.Height;

            // adornerWidth & adornerHeight are used for placement as well.
            double adornerWidth = this.DesiredSize.Width;
            double adornerHeight = this.DesiredSize.Height;

            topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
            topRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
            bottomLeft.Arrange(new Rect(-adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));
            bottomRight.Arrange(new Rect(desiredWidth - adornerWidth / 2, desiredHeight - adornerHeight / 2, adornerWidth, adornerHeight));

            // Return the final size.
            return finalSize;
        }

        // Helper method to instantiate the corner Thumbs, set the Cursor property, 
        // set some appearance properties, and add the elements to the visual tree.
        void BuildAdornerCorner(ref Thumb cornerThumb, Cursor customizedCursor)
        {
            if (cornerThumb != null) return;

            cornerThumb = new Thumb();
            cornerThumb.Height = cornerThumb.Width = 10;
            cornerThumb.Opacity = 0.40;
            cornerThumb.Background = BrushCache.GetBrush(Colors.MediumBlue);

            if (allowResize)
            {
                cornerThumb.Cursor = customizedCursor;

                cornerThumb.PreviewMouseLeftButtonDown += cornerThumb_MouseButtonEvent;
                cornerThumb.PreviewMouseLeftButtonUp += cornerThumb_MouseButtonEvent;
            }
            
            visualChildren.Add(cornerThumb);
        }

        void cornerThumb_MouseButtonEvent(object sender, MouseButtonEventArgs e)
        {
            var handler = MouseEvent;
            if (handler != null)
                handler(e);
        }

        // This method ensures that the Widths and Heights are initialized.  Sizing to content produces
        // Width and Height values of Double.NaN.  Because this Adorner explicitly resizes, the Width and Height
        // need to be set first.  It also sets the maximum size of the adorned element.
        void EnforceSize(FrameworkElement adornedElement)
        {
            if (adornedElement.Width.Equals(Double.NaN))
                adornedElement.Width = adornedElement.DesiredSize.Width;
            if (adornedElement.Height.Equals(Double.NaN))
                adornedElement.Height = adornedElement.DesiredSize.Height;

            FrameworkElement parent = adornedElement.Parent as FrameworkElement;
            if (parent != null)
            {
                adornedElement.MaxHeight = parent.ActualHeight;
                adornedElement.MaxWidth = parent.ActualWidth;
            }
        }

        // Override the VisualChildrenCount and GetVisualChild properties to interface with 
        // the adorner's visual collection.
        protected override int VisualChildrenCount { get { return visualChildren.Count; } }
        protected override Visual GetVisualChild(int index) { return visualChildren[index]; }
    }
}
